package com.swang.security.demo.util;

import com.alibaba.fastjson.JSON;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import javax.net.ssl.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @ClassName: WebUtils
 * @Description: http请求工具类
 * @author swang
 * @date 2017年4月20日 上午12:29:11
 */
public class WebUtils {

    private static final Logger LOGGER = Logger.getLogger(WebUtils.class);

    private static final String     DEFAULT_CHARSET = "UTF-8";

    private static final String     METHOD_POST     = "POST";

    private static final String     METHOD_GET      = "GET";


    private static SSLContext ctx             = null;

    private static HostnameVerifier verifier        = null;

    private static SSLSocketFactory socketFactory   = null;

    private static class DefaultTrustManager implements X509TrustManager {
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

        public void checkClientTrusted(X509Certificate[] chain,
                                       String authType) throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain,
                                       String authType) throws CertificateException {
        }
    }

    static {

        try {
            ctx = SSLContext.getInstance("TLS");
            ctx.init(new KeyManager[0], new TrustManager[] { new DefaultTrustManager() },
                    new SecureRandom());

            ctx.getClientSessionContext().setSessionTimeout(15);
            ctx.getClientSessionContext().setSessionCacheSize(1000);

            socketFactory = ctx.getSocketFactory();
        } catch (Exception e) {

        }

        verifier = new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return false;//默认认证不通过，进行证书校验。
            }
        };

    }

    /**
     * @Title:doPost
     * @Description:post请求
     * @param url 请求的url
     * @param content 请求的json串
     * @param connectTimeout 连接超时时间
     * @param readTimeout 数据读取超时时间
     * @Param headerMap 头参数
     * @return:java.lang.String 响应字符串
     * @throws:
     */
    public static String doPost(String url, String content, int connectTimeout,
                                int readTimeout, Map<String, String> headerMap) throws IOException {
        return doPost(url, content, DEFAULT_CHARSET, connectTimeout, readTimeout, headerMap);
    }

    /**
     * @Title:doPost
     * @Description:执行post
     * @param url 请求的url
     * @param content 请求的json串
     * @param charset 字符集
     * @param connectTimeout 连接超时时间
     * @param readTimeout 数据读取超时时间
     * @Param headerMap 头参数
     * @return:java.lang.String 响应字符串
     * @throws:
     */
    public static String doPost(String url, String content, String charset,
                                int connectTimeout, int readTimeout, Map<String, String> headerMap) throws IOException {

        String ctype = "application/json;charset=" + charset;
        byte[] contentByte = {};
        if (StringUtils.isNotBlank(content)) {
            contentByte = content.getBytes(charset);
        }
        return doPost(url, ctype, contentByte, connectTimeout, readTimeout, headerMap);
    }

    
    /**
     * @Title:doPost
     * @Description:post请求
     * @param url 请求的url
     * @param ctype
     * @param content 请求内容
     * @param connectTimeout 连接超时时间
     * @param readTimeout 数据读入超时时间
     * @Param headerMap 头参数
     * @return:java.lang.String
     * @throws:
     */
    public static String doPost(String url, String ctype, byte[] content, int connectTimeout,
                                int readTimeout, Map<String, String> headerMap) throws IOException {

        HttpURLConnection conn = null;
        OutputStream out = null;
        String rsp = null;
        try {
            try {
                conn = getConnection(new URL(url), METHOD_POST, ctype, headerMap);
                conn.setConnectTimeout(connectTimeout);
                conn.setReadTimeout(readTimeout);
            } catch (IOException e) {
                //Map<String, String> map = getParamsFromUrl(url);
                LoggerUtils.debug(LOGGER, "获取http连接失败 url:{0} headerMap:{1} content:{2}",
                        url, JSON.toJSONString(headerMap), new String(content));
                throw e;
            }
            try {
                out = conn.getOutputStream();
                out.write(content);
                rsp = getResponseAsString(conn);
            } catch (IOException e) {
                //Map<String, String> map = getParamsFromUrl(url);
                LoggerUtils.debug(LOGGER, "请求接口失败 url:{0} headerMap:{1} content:{2}",
                        url, JSON.toJSONString(headerMap), new String(content));
                throw e;
            }

        } finally {
            if (out != null) {
                out.close();
            }
            if (conn != null) {
                conn.disconnect();

            }
        }

        return rsp;
    }

    /**
     * @Title:doGet
     * @Description:使用默认的字符集的get请求
     * @param url 请求的url
     * @param params 请求的参数
     * @Param headerMap 头参数
     * @return:java.lang.String 返回的响应数据
     * @throws:
     */
    public static String doGet(String url, Map<String, String> params, Map<String, String> headerMap) throws IOException {
        return doGet(url, params, DEFAULT_CHARSET, headerMap);
    }

    /**
     * @Title:doGet
     * @Description:get请求
     * @param url 请求的url地址
     * @param params 请求参数
     * @param charset 字符集
     * @Param headerMap 头参数
     * @return:java.lang.String 返回的响应数据
     * @throws:
     */
    public static String doGet(String url, Map<String, String> params,
                               String charset, Map<String, String> headerMap) throws IOException {
        HttpURLConnection conn = null;
        String rsp = null;

        try {
            String ctype = "application/x-www-form-urlencoded;charset=" + charset;
            String query = buildQuery(params, charset);
            try {
                conn = getConnection(buildGetUrl(url, query), METHOD_GET, ctype, headerMap);
            } catch (IOException e) {
                Map<String, String> map = getParamsFromUrl(url);
                LoggerUtils.debug(LOGGER, "获取http-get连接失败 url:{0} param:{1}",
                        url, JSON.toJSONString(map));
                throw e;
            }

            try {
                rsp = getResponseAsString(conn);
            } catch (IOException e) {
                Map<String, String> map = getParamsFromUrl(url);
                LoggerUtils.debug(LOGGER, "http-get请求失败 url:{0} param:{1}",
                        url, JSON.toJSONString(map));
                throw e;
            }

        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }

        return rsp;
    }

    /**
     * @Title:getConnection
     * @Description:获取http链接
     * @param url  请求的url
     * @param method 对用请求的方法，目前支持get/post
     * @param ctype content-type
     * @param headerParam 头参数
     * @return:java.net.HttpURLConnection
     * @throws:
     */
    private static HttpURLConnection getConnection(URL url, String method,
                                                   String ctype, Map<String, String> headerParam) throws IOException {

        HttpURLConnection conn = null;
        if ("https".equals(url.getProtocol())) {
            HttpsURLConnection connHttps = (HttpsURLConnection) url.openConnection();
            connHttps.setSSLSocketFactory(socketFactory);
            connHttps.setHostnameVerifier(verifier);
            conn = connHttps;
        } else {
            conn = (HttpURLConnection) url.openConnection();
        }
        conn.setRequestMethod(method);
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setRequestProperty("Accept", "application/json");
        conn.setRequestProperty("User-Agent", "yuntai-sdk-java");
        conn.setRequestProperty("Content-Type", ctype);

        //设置自定义头参数
        if(headerParam != null){
            Set<Map.Entry<String, String>> paramSet = headerParam.entrySet();

            for(Map.Entry<String, String> entry : paramSet){

                String key = entry.getKey();
                String value = entry.getValue();
                if(StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)){
                    conn.setRequestProperty(key, value);
                }
            }
        }

        return conn;
    }

    /**
     * @Title:buildGetUrl
     * @Description:构建get请求的url
     * @param strUrl 请求的url
     * @param query  请求的参数
     * @return:java.net.URL
     * @throws:
     */
    public static URL buildGetUrl(String strUrl, String query) throws IOException {
        URL url = new URL(strUrl);
        if (StringUtils.isEmpty(query)) {
            return url;
        }

        if (StringUtils.isEmpty(url.getQuery())) {
            if (strUrl.endsWith("?")) {
                strUrl = strUrl + query;
            } else {
                strUrl = strUrl + "?" + query;
            }
        } else {
            if (strUrl.endsWith("&")) {
                strUrl = strUrl + query;
            } else {
                strUrl = strUrl + "&" + query;
            }
        }

        return new URL(strUrl);
    }

    /**
     * @Title:buildQuery
     * @Description:构建url get请求的参数
     * @param params 参数
     * @param charset 字符集
     * @return:java.lang.String 返回的url
     * @throws:
     */
    public static String buildQuery(Map<String, String> params, String charset) throws IOException {

        if (params == null || params.isEmpty()) {
            return null;
        }

        StringBuilder query = new StringBuilder();
        Set<Map.Entry<String, String>> entries = params.entrySet();
        boolean hasParam = false;

        for (Map.Entry<String, String> entry : entries) {
            String name = entry.getKey();
            String value = entry.getValue();
            // 忽略参数名或参数值为空的参数
            if (StringUtils.isNotEmpty(name) && StringUtils.isNotEmpty(value) ) {
                if (hasParam) {
                    query.append("&");
                } else {
                    hasParam = true;
                }

                query.append(name).append("=").append(URLEncoder.encode(value, charset));
            }
        }

        return query.toString();
    }

    /**
     * @Title:getResponseAsString
     * @Description:获取http连接中的响应的字符串
     * @param conn http连接
     * @return:java.lang.String 返回响应的字符串
     * @throws:
     */
    protected static String getResponseAsString(HttpURLConnection conn) throws IOException {
        String charset = getResponseCharset(conn.getContentType());
        InputStream es = conn.getErrorStream();
        if (es == null) {
            return getStreamAsString(conn.getInputStream(), charset);
        } else {
            String msg = getStreamAsString(es, charset);
            if (StringUtils.isEmpty(msg)) {
                throw new IOException(conn.getResponseCode() + ":" + conn.getResponseMessage());
            } else {
                throw new IOException(msg);
            }
        }
    }

    /**
     * @Title:getStreamAsString
     * @Description:从输出流中获取对用字符集的字符串
     * @param stream
     * @param charset
     * @return:java.lang.String
     * @throws:
     */
    private static String getStreamAsString(InputStream stream, String charset) throws IOException {
        try {

            BufferedReader reader = new BufferedReader(new InputStreamReader(stream, charset));
            StringWriter writer = new StringWriter();

            char[] chars = new char[256];
            int count = 0;

            while((count = reader.read(chars)) > 0){
                writer.write(chars, 0, count);
            }

            return writer.toString();
        } finally {
            if (stream != null) {
                stream.close();
            }
        }
    }

    /**
     * @Title:getResponseCharset
     * @Description:获取响应数据中数据集
     * @param ctype
     * @return:java.lang.String
     * @throws:
     */
    private static String getResponseCharset(String ctype) {

        String charset = DEFAULT_CHARSET;

        if (!StringUtils.isEmpty(ctype)) {
            String[] params = ctype.split(";");
            for (String param : params) {
                param = param.trim();
                if (param.startsWith("charset")) {
                    String[] pair = param.split("=", 2);
                    if (pair.length == 2) {
                        if (!StringUtils.isEmpty(pair[1])) {
                            charset = pair[1].trim();
                        }
                    }
                    break;
                }
            }
        }

        return charset;
    }

    /**
     * @Title:decode
     * @Description:使用默认的UTF-8字符集反编码请求参数值。
     * @param value 参数值
     * @return:java.lang.String 解码后的值
     * @throws:
     */
    public static String decode(String value) {
        return decode(value, DEFAULT_CHARSET);
    }

    /**
     * @Title:encode
     * @Description: 使用默认的UTF-8字符集编码请求参数值。
     * @param value 参数值
     * @return:java.lang.String 编译后的字符串
     * @throws:
     */
    public static String encode(String value) {
        return encode(value, DEFAULT_CHARSET);
    }

    /**
     * @Title:decode
     * @Description:
     * @param value 参数值
     * @param charset 字符集
     * @return:java.lang.String 解码后的参数值
     * @throws:
     */
    public static String decode(String value, String charset) {

        String result = null;
        if (!StringUtils.isEmpty(value)) {
            try {
                result = URLDecoder.decode(value, charset);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return result;
    }

    /**
     * @Title:encode
     * @Description: 使用指定的字符集编码请求参数值。
     * @param value 参数值
     * @param charset 字符集
     * @return:java.lang.String 编码后的参数值
     * @throws:
     */
    public static String encode(String value, String charset) {

        String result = null;
        if (!StringUtils.isEmpty(value)) {
            try {
                result = URLEncoder.encode(value, charset);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return result;
    }

    /**
     * @Title:getParamsFromUrl
     * @Description:从请求的完整url中获取参数
     * @param url 完整的url
     * @return:java.util.Map<java.lang.String,java.lang.String> 映射的参数
     * @throws:
     */
    public static Map<String, String> getParamsFromUrl(String url) {

        Map<String, String> map = null;
        if (url != null && url.indexOf('?') != -1) {
            map = splitUrlQuery(url.substring(url.indexOf('?') + 1));
        }
        if (map == null) {
            map = new HashMap<String, String>();
        }
        return map;
    }

    /**
     * @Title:splitUrlQuery
     * @Description:从请求的url参数部分中获取所有参数
     * @param query url地址中的参数部分
     * @return:java.util.Map<java.lang.String,java.lang.String> 参数映射
     * @throws:
     */
    public static Map<String, String> splitUrlQuery(String query) {

        Map<String, String> result = new HashMap<String, String>();
        if(StringUtils.isNotBlank(query)) {
            String[] pairs = query.split("&");
            if (pairs != null && pairs.length > 0) {
                for (String pair : pairs) {
                    String[] param = pair.split("=", 2);
                    if (param != null && param.length == 2) {
                        result.put(param[0], param[1]);
                    }
                }
            }
        }

        return result;
    }


    /**
     * @Title:getRequestUrl
     * @Description:获取请求的url
     * @param req
     * @return:java.lang.String
     * @throws:
     */
    public static <T extends Response> String getRequestUrl(String serverUrl, Request<T> req){

        StringBuilder urlSb = new StringBuilder(serverUrl).append(req.getInterfaceName());
        try {
            String paramContent = WebUtils.buildQuery(req.getBodyParam(), DEFAULT_CHARSET);

            if(StringUtils.isNotBlank(paramContent)){
                urlSb.append("?").append(paramContent);
            }
            return urlSb.toString();
        } catch (IOException e) {
            LoggerUtils.error(e, LOGGER, "构建请求参数异常 codeRequest：{0}", JSON.toJSONString(req));
            return null;
        }
    }

    /**
     * @Title:getOldRequestUrl
     * @Description:获取request的url
     * @param req
     * @return:java.lang.String
     * @throws:
     */
    public static String getOldRequestUrl(HttpServletRequest req, String ...removeKey) throws IOException {

        StringBuffer oldUrl = new StringBuffer();
        oldUrl.append(req.getRequestURL().toString());
        Map<String, String> queryMap = splitUrlQuery(req.getQueryString());

        for(int i = 0; i < removeKey.length; i++){
            if(StringUtils.isBlank(removeKey[i])){
                continue;
            }
            queryMap.remove(removeKey[i]);
        }

        String queryStr = buildQuery(queryMap, DEFAULT_CHARSET);
        if(StringUtils.isNotBlank(queryStr)){
            oldUrl.append("?").append(queryStr);
        }
        LoggerUtils.debug(LOGGER, "获取的回调地址 url:{0}", oldUrl.toString());

        return oldUrl.toString();
    }

    /**
     * @Title:getOldRequestUrl
     * @Description:获取request的url, 并判断是否需要base64加密
     * @param req
     * @param isNeedBase64Encode
     * @return:java.lang.String
     * @throws:
     */
    public static String getOldRequestUrl(HttpServletRequest req, boolean isNeedBase64Encode, String ...removeKey) throws IOException {
        if(isNeedBase64Encode){
            return Base64.encodeBase64URLSafeString(getOldRequestUrl(req, removeKey).getBytes());
        }else{
            return getOldRequestUrl(req, removeKey);
        }
    }
}
